#include "znx_inet.h"


static inline znx_bool_t
znx_address_port_parser(const u_char *start, const u_char *end, int *port)
{
    int tport = 0;
    while (start != end) {
        if (*(start) == ':') {
            start++;
            continue;
        }

        if (*start < '0' || *start > '9') {
            return ZNX_FALSE;
        }
        tport = tport * 10 + (*start - '0');
        start++;
    }

    *port = tport;
    return ZNX_TRUE;
}


znx_bool_t
znx_address_parser(u_char *addr_str,
    size_t addr_len, znx_address_t *address, int *port)
{
    if (port != NULL) {
        *port = 0;
    }

    // it's a unix domain address
    if (addr_len > ZNX_UNIX_ADDRESS_PREFIX_LEN &&
        znx_strncmp(addr_str, ZNX_UNIX_ADDRESS_PREFIX, ZNX_UNIX_ADDRESS_PREFIX_LEN) == 0) {
        if (addr_len - ZNX_UNIX_ADDRESS_PREFIX_LEN > ZNX_UNIX_ADDR_PATH_SIZE - 1) {
            return ZNX_FALSE;
        }
        address->sockaddr_un.sun_family = AF_UNIX;
        znx_memcpy(address->sockaddr_un.sun_path, addr_str + ZNX_UNIX_ADDRESS_PREFIX_LEN,
            addr_len - ZNX_UNIX_ADDRESS_PREFIX_LEN);
        address->sockaddr_un.sun_path[addr_len - ZNX_UNIX_ADDRESS_PREFIX_LEN] = '\0';
        return ZNX_TRUE;
    }

    int     i;
    int     tport = 0;
    int     addr_end_pos = 0;
    u_char  tmp_addr[ZNX_INET6_ADDRSTRLEN] = { 0 };

    // it's a ipv6 address
    if (addr_len > 1 && *addr_str == '[') {
        for (i = 1; i < (int64_t)addr_len; i++) {
            if (*(addr_str + i) == ']') {
                addr_end_pos = i;
                break;
            }
        }

        if (addr_end_pos == 0) {
            return ZNX_FALSE;
        }

        addr_end_pos += 1;
        if (addr_end_pos != (int64_t)addr_len &&
            !znx_address_port_parser(addr_str + addr_end_pos, addr_str + addr_len, &tport)) {
            return ZNX_FALSE;
        }

        if (port != NULL) {
            *port = tport;
        }
        
        address->sockaddr_in6.sin6_family = AF_INET6;
        address->sockaddr_in6.sin6_port = htons((uint16_t)tport);
        znx_memcpy(tmp_addr, addr_str + 1, (size_t)addr_end_pos - 2); // skip '[' and ']'
        tmp_addr[addr_end_pos - 2] = '\0';

        int res = inet_pton(AF_INET6, (char *)tmp_addr,
            &address->sockaddr_in6.sin6_addr);
        if (res != 1) { 
            return ZNX_FALSE;
        }

        return ZNX_TRUE;
    }

    // it's a ipv4 address
    addr_end_pos = 0;
    for (i = 0; i < (int64_t)addr_len; i++) {
        if (*(addr_str+ i) != ':') {
            addr_end_pos++;
            continue;
        }

        break;
    }

    if (addr_end_pos != (int64_t)addr_len &&
        !znx_address_port_parser(addr_str + addr_end_pos, addr_str + addr_len, &tport)) {
        return ZNX_FALSE;
    }

    if (port != NULL) {
        *port = tport;
    }

    address->sockaddr_in.sin_family = AF_INET;
    address->sockaddr_in.sin_port = htons((uint16_t)tport);

    int res;
    if (addr_end_pos != 0) {
        znx_memcpy(tmp_addr, addr_str, (size_t)addr_end_pos);
        tmp_addr[addr_end_pos] = '\0';
        if (*tmp_addr == '\0') {
            znx_memcpy(tmp_addr, "0.0.0.0", 7);
            tmp_addr[7] = '\0';
        }
        res = inet_pton(AF_INET, (char *)tmp_addr, &address->sockaddr_in.sin_addr);
    } else {
        if (*tmp_addr == '\0') {
            znx_memcpy(tmp_addr, "0.0.0.0", 7);
            tmp_addr[7] = '\0';
        }
        res = inet_pton(AF_INET, (char *)tmp_addr, &address->sockaddr_in.sin_addr);
    }

    if (res != 1) {
        return ZNX_FALSE;
    }

    return ZNX_TRUE;
}


znx_bool_t
znx_address_init_by_sockaddr(znx_address_t *address,
    znx_sockaddr_t *sockaddr)
{
    if (sockaddr->sa_family == AF_UNIX) {
        znx_memcpy(&address->sockaddr_un, sockaddr, sizeof(znx_sockaddr_un_t));
        return ZNX_TRUE;
    }

    if (sockaddr->sa_family == AF_INET) {
        znx_memcpy(&address->sockaddr_in, sockaddr, sizeof(znx_sockaddr_in_t));
        return ZNX_TRUE;
    }

    if (sockaddr->sa_family == AF_INET6) {
        znx_memcpy(&address->sockaddr_in6, sockaddr, sizeof(znx_sockaddr_in6_t));
        return ZNX_TRUE;
    }

    return ZNX_FALSE;
}


int
znx_sockaddr_to_str(znx_sockaddr_t *addr, u_char *dest, int dest_size)
{
    if (addr->sa_family == AF_UNIX) {
        znx_sockaddr_un_t *sockaddr_un = (znx_sockaddr_un_t *)addr;
        size_t len;
        if (!znx_strlen_try((u_char *)sockaddr_un->sun_path,
            sizeof(znx_sockaddr_un_t) - offsetof(znx_sockaddr_un_t, sun_path), &len)) {
            return 0;
        }
        return snprintf((char *)dest, (size_t)dest_size,
            "unix:%s", sockaddr_un->sun_path);
    }

    int     len, n;
    const   u_char *res;

    if (addr->sa_family == AF_INET) {
        znx_sockaddr_in_t *sockaddr_in = (znx_sockaddr_in_t *)addr;
        res = (const u_char *)inet_ntop(AF_INET, &sockaddr_in->sin_addr,
            (char *)dest, (socklen_t)dest_size);
        if (!res) {
            return 0;
        }

        len = (int)znx_strlen(dest);
        n = snprintf((char *)dest + len, (size_t)(dest_size - len), ":%d",
            ntohs(sockaddr_in->sin_port));
        return len + n;
    }

    if (addr->sa_family == AF_INET6) {
        znx_sockaddr_in6_t *sockaddr_in6 = (znx_sockaddr_in6_t *)addr;
        *dest = '[';
        res = (const u_char *)inet_ntop(AF_INET6, &sockaddr_in6->sin6_addr,
            (char *)dest+1, (socklen_t)(dest_size - 1));
        if (!res) {
            return 0;
        }

        len = (int)znx_strlen(dest);
        n = snprintf((char *)dest + len, (size_t)(dest_size - len), "]:%d",
            ntohs(sockaddr_in6->sin6_port));
        return len + n;
    }

    return 0;
}


znx_bool_t
znx_str_maybe_is_ipv4(znx_str_t *str)
{
    u_char c;
    for (size_t i = 0; i < str->len; i++) {
        c = *(str->data + i);
        if (c == '.') {
            continue;
        }
        if (c < '0' || c > '9') {
            return ZNX_FALSE;
        }
    }

    return ZNX_TRUE;
}

